// ==UserScript==
// @name         okex-c2c-auto-trade
// @namespace    https://www.okex.com/fiat/c2c
// @version      1.0.0
// @description  okex 点对点 usdt 自动交易
// @author       luojun
// @match        https://www.okex.com/fiat/c2c
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    (function () {
        //时间格式化
        //(new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006 - 07 - 02 08: 09: 04.423
        //(new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006 - 7 - 2 8: 9: 4.18
        Date.prototype.Format = function (fmt) {
            var o = {
                "M+": this.getMonth() + 1, //月份
                "d+": this.getDate(), //日
                "h+": this.getHours(), //小时
                "m+": this.getMinutes(), //分
                "s+": this.getSeconds(), //秒
                "q+": Math.floor((this.getMonth() + 3) / 3), //季度
                "S": this.getMilliseconds() //毫秒
            };
            if (/(y+)/.test(fmt))
                fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
            for (var k in o) {
                if (new RegExp("(" + k + ")").test(fmt)) {
                    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
                }
            }
            return fmt;
        }
    })();
    class WebUtils {
        /**
         * 网页通知
         *
         * @static
         * @param {string} title 标题
         * @param {string} body 内容
         * @param {string} icon 图标链接
         * @param {string} sound 提示音链接
         * @memberof WebUtils
         */
        static notify(title, body, icon, sound) {
            Notification.requestPermission(function (result) {
                if (result == 'granted') {
                    const notification = new Notification(title, {
                        body: body,
                        icon: icon,
                        silent: true,
                    });
                    if (sound) {
                        //用h5播放
                        const okexAudio = new Audio(sound);
                        okexAudio.play();
                    }
                    notification.onclick = function () {
                        notification.close();
                    };
                } else {
                    alert('通知提醒已禁止,如需启用，请在浏览器选项里启用')
                }
            });
        }

        /**
         * 元素监听
         *
         * @static
         * @param {object} ele document.querySelector('')
         * @param {func} callback 回调
         * @param {boolean} [observeConfig={
         *         attributes: true,
         *         childList: true
         *     }] 监听配置
         * @returns
         * @memberof WebUtils
         */
        static observe(ele, callback, observeConfig = {
            attributes: true,
            childList: true
        }) {
            const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; //浏览器兼容
            const observer = new MutationObserver(function (mutations) { //构造函数回调
                mutations.forEach(function (record) {
                    callback(record);
                });
            });
            observer.observe(ele, observeConfig);
            return observer;
        }

        /**
         * WebSocket连接
         *
         * @static
         * @param {string} serverIP 服务器ip
         * @param {number} serverPort 服务器端口
         * @returns WebSocket实例
         * @memberof WebUtils
         */
        static webSocketConnect(serverIP, serverPort) {
            const self = this;
            const host = `ws://${serverIP}:${serverPort}/`;
            let socket;
            try {
                socket = new WebSocket(host);
                socket.onopen = function (msg) {
                    self.WebUtilsSocket = socket;
                    self.log(`socket连接成功`, 1);
                };
                socket.onmessage = function (msg) {
                    if (typeof msg.data === "string") {
                        self.log(`收到服务器消息:${msg.data}`, 1);
                    } else {
                        self.log(`消息类型有误`, 1);
                    }
                };
                socket.onclose = function (msg) {
                    self.log(`socket连接关闭`, 1);
                };
            } catch (error) {
                self.log(error);
            }
            return socket;
        }

        /**
         * WebSocket发送消息
         *
         * @static
         * @param {any} socket WebSocket实例
         * @param {string} msg 消息
         * @memberof WebUtils
         */
        static webSocketSend(socket, msg) {
            if (socket) {
                const endTokens = '\0';
                msg = `${msg}${endTokens}`;
                socket.send(msg);
            }
        }

        /**
         * 日志
         *
         * @static
         * @param {string} msg 日志信息
         * @param {number} [logLever=2] 0<1<2<3<4<5 => ALL < DEBUG < INFO < WARN < ERROR < FATAL
         * @memberof WebUtils
         */
        static log(msg, logLever = 2) {
            const self = this;
            if (typeof msg === 'object') {
                msg = JSON.stringify(msg);
            }
            console.log(`${(new Date()).Format("yyyy-MM-dd hh:mm:ss.S")}:${msg}`);
            if (logLever > 1 && !!self.WebUtilsSocket && self.WebUtilsSocket.readyState === 1) {
                self.webSocketSend(self.WebUtilsSocket, `jslog:${msg}`);
            }
        }

        /**
         * 异步请求
         *
         * @static
         * @param {function} func 需异步请求的函数，剩余参数传函数所需参数
         * @returns
         * @memberof WebUtils
         */
        static taskRun(func) {
            return new Promise((resolve, reject) => {
                if (typeof func !== 'function') {
                    reject(`函数有误`);
                    return;
                }
                setTimeout(() => {
                    try {
                        const thatArgs = [...arguments];
                        const funcArgs = thatArgs.splice(1);
                        resolve(func(...funcArgs));
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            });
        }
    }

    class OkexHelper {

        /**
         * Creates an instance of OkexHelper.
         * @param {number} [autoBuyPrice=0] 小于等于时自动买入
         * @param {number} [autoSellPrice=99999999999] 大于等于时自动卖出
         * @param {number} [diffPrice=99999999999] 买1买2或卖1卖2存在价格大于等于配置,且买1大于卖1时，自动买卖
         * @param {number} [maxAutoTradeAmt=0] 最大交易额度/人民币
         * @param {string} [icon='https://img.bafang.com/v_20180123212400/okex/image/common/logo_okex_v2.png'] 提示icon
         * @param {string} [sound='http://data.huiyi8.com/yinxiao/mp3/83766.mp3'] 提示音配置
         * @param {boolean} [isDebug=false] 是否调试模式
         * @memberof OkexHelper
         */
        constructor({
            autoBuyPrice = 0,
            autoSellPrice = 99999999999,
            diffPrice = 99999999999,
            maxAutoTradeAmt = 0,
            icon = 'https://img.bafang.com/v_20180123212400/okex/image/common/logo_okex_v2.png',
            sound = 'http://data.huiyi8.com/yinxiao/mp3/83766.mp3',
            isAutoSell = true,
            isDebug = false,
            finishRate = 0.8, //卖家订单完成率，低于此配置，不自动接单
            startOfJob = '08:30',
            endOfJob = '23:59',
            autoRefreshInterval = 60 * 60,
        }) {
            this.config = {
                autoBuyPrice: autoBuyPrice,
                autoSellPrice: autoSellPrice,
                diffPrice: diffPrice,
                maxAutoTradeAmt: maxAutoTradeAmt,
                icon: icon,
                sound: sound,
                isAutoSell: isAutoSell,
                isDebug: isDebug,
                attributes: true,
                childList: true,
                orderTradeBuyObserve: null,
                orderTradeSellObserve: null,
                finishRate: finishRate,
                canContinuedBuying: true, //可继续买入
                okexNotifySocket: WebUtils.webSocketConnect('127.0.0.1', 1234),
                checkUserStatusTimer: null,
                tempDiffPrice: diffPrice,
                orderStatus: {
                    UnFinish: 0,
                    Finished: 1,
                    Canceled: 2
                },
                sellFailed: false, //卖出失败
                sellFailedOrderAmount: 0, //卖出失败数量
                startOfJob: startOfJob, //工作开始时间
                startHoursOfJob: 0,
                startMinutesOfJob: 0,
                endOfJob: endOfJob, //工作结束时间
                endHoursOfJob: 0,
                endMinutesOfJob: 0,
                thawingTime: new Date(), //账号冻结后解冻时间
                availableUSDT: 0, //可用usdt
                unFinishedOrderCount: 0, //未完成订单数
                nowBuyPrice: 0, //现在购买价格
                isOrdering: false, //正在下单中
                autoRefreshInterval: autoRefreshInterval, //自动刷新时间间隔 单位/秒
                lastRefreshTime: Date.now(),
            };
            const startOfJobArr = this.config.startOfJob.split(':');
            this.config.startHoursOfJob = Number(startOfJobArr[0]);
            this.config.startMinutesOfJob = Number(startOfJobArr[1]);
            const endOfJobArr = this.config.endOfJob.split(':');
            this.config.endHoursOfJob = Number(endOfJobArr[0]);
            this.config.endMinutesOfJob = Number(endOfJobArr[1]);
        }

        /**
         * okex网页通知
         *
         * @param {string} title 标题
         * @param {string} body 内容
         * @memberof OkexHelper
         */
        okexNotify(title, body) {
            const self = this;
            WebUtils.notify(title, body, self.config.icon, self.config.sound);
        }

        /**
         * 发送提醒(邮件、短信、qq等)
         *
         * @param {string} msg 提醒内容
         * @param {string} [type='autotrade']  提醒类型 autotrade:自动交易提醒
         * @memberof OkexHelper
         */
        sendReminder(msg, type = 'autotrade') {
            const self = this;
            WebUtils.webSocketSend(self.config.okexNotifySocket, `${type}:${msg}`);
        }

        /**
         * 转换为数字
         *
         * @param {string} str 有千分位等的数字字符串
         * @param {number} [length=2] 四舍五入后长度
         * @returns
         * @memberof OkexHelper
         */
        toNum(str, length = 2) {
            const self = this;
            return self.mathRound(Number(str.split(',').join('')), length);
        }

        /**
         * 四舍五入
         *
         * @param {number} num 原数字
         * @param {number} [length=2] 四舍五入后长度
         * @returns
         * @memberof OkexHelper
         */
        mathRound(num, length = 2) {
            return parseFloat(num.toFixed(length));
        }

        /**
         * 自动交易
         *
         * @memberof OkexHelper
         */
        autoOrderTrade() {
            const self = this;
            //配置通知
            let configNotifyMsg = `价格小于等于${self.config.autoBuyPrice}时自动买入\r\n`;
            if (self.config.isAutoSell) {
                configNotifyMsg += `价格大于等于${self.config.autoSellPrice}时自动卖出\r\n`;
            }
            configNotifyMsg += `买卖价格差异大于等于${self.config.diffPrice}时自动买卖\r\n买卖最大金额为：￥${self.config.maxAutoTradeAmt}`;
            WebUtils.log(configNotifyMsg);
            self.okexNotify('Okex-C2C自动买卖配置提醒', configNotifyMsg);
            const _oldFetch = window.fetch;
            window.fetch = function () {
                const reqArgs = arguments;
                const newFetch = _oldFetch.apply(window, arguments).then((res) => {
                    return res.json().then((json) => {
                        self.handleResponse(reqArgs, json);
                        //覆盖原json函数，解决流已读问题
                        res.json = function () {
                            return json;
                        };
                        return res;
                    });
                });
                return newFetch;
            }
        }

        handleResponse(reqArgs, result) {
            const self = this;
            if (!reqArgs || !result || result.code !== 0) {
                WebUtils.log(JSON.stringify(reqArgs), 4);
                WebUtils.log(JSON.stringify(result), 4);
                if (result.Code === 403) {
                    self.sendReminder('登录状态已失效，请及时重试登录');
                }
                return;
            }
            const reqUrl = reqArgs[0];
            const reqParams = reqArgs[1];
            const UrlConst = self.getUrlConst();
            //用户资产
            const userAssetUrl = UrlConst.GET_USER_ASSET.replace('{0}', 'usdt');
            if (reqUrl.includes(userAssetUrl)) {
                self.handleUserAsset(result);
            }
            //交易监听
            if (reqUrl.includes(UrlConst.GET_TRADING_ORDER)) {
                self.handleTradeOrder(result);
            }
            //未完成订单
            if (reqUrl.includes("/c2c-open/orders?status=0")) {
                self.handleUnFinishedOrder(result);
                console.clear();
            }
        }

        /**
         * 用户资产
         *
         * @param {any} result
         * @memberof OkexHelper
         */
        handleUserAsset(result) {
            const self = this;
            if (self.config.isDebug) {
                debugger;
            }
            self.config.availableUSDT = Math.round(result.data.available);
        }

        /**
         * 未完成订单数记录
         *
         * @param {any} result
         * @memberof OkexHelper
         */
        handleUnFinishedOrder(result) {
            const self = this;
            if (self.config.isDebug) {
                debugger;
            }
            self.config.unFinishedOrderCount = 0;
            let notifyMsg = 'Okex C2C 交易 ';
            let isNotify = false;
            result.data.items.forEach((unFinishedOrder) => {
                if (unFinishedOrder.buy) {
                    self.config.unFinishedOrderCount += 1;
                }
                const notifyTimeInterval = 60 * 3; //3分钟一次
                //卖出失败的情况
                if (result.data.items.length === 1 && unFinishedOrder.buy && unFinishedOrder.paidDate === 0) {
                    const nowBuyPrice = self.config.nowBuyPrice;
                    //低价买入，且未付款时，定时邮件通知付款
                    if (unFinishedOrder.exchangeRate < nowBuyPrice && unFinishedOrder.expiredAfterSeconds % notifyTimeInterval < 8) {
                        const msg = `Okex C2C 交易订单：${unFinishedOrder.publicOrderId}，买入价：￥${unFinishedOrder.exchangeRate},现价：￥${nowBuyPrice},存在利润，请及时付款买入`;
                        WebUtils.log(msg);
                        if (!self.config.isDebug) {
                            self.sendReminder(msg);
                        }
                    }
                }
                //成功买卖，未付款
                if (result.data.items.length > 1) {
                    notifyMsg += `订单：${unFinishedOrder.publicOrderId}，${unFinishedOrder.buy ? '买入' : '卖出'}价：￥${unFinishedOrder.exchangeRate},${unFinishedOrder.paidDate === 0 ? '未付款;' : '已付款;'}`;
                    if (unFinishedOrder.buy) {
                        const sellerAccount = unFinishedOrder.sellerAllReceiptAccountList[0];
                        notifyMsg += `卖家:${sellerAccount.accountName}，卡号：${sellerAccount.accountNo},收款银行：${sellerAccount.bankName};`;
                        if (unFinishedOrder.expiredAfterSeconds > 0 && unFinishedOrder.expiredAfterSeconds % notifyTimeInterval < 8) {
                            isNotify = true;
                        }
                    }
                }
            });
            if (isNotify) {
                WebUtils.log(notifyMsg);
                if (!self.config.isDebug) {
                    self.sendReminder(notifyMsg);
                }
            }
        }

        /**
         * 交易
         *
         * @param {any} result
         * @returns
         * @memberof OkexHelper
         */
        handleTradeOrder(result) {
            const self = this;
            //是否选中了usdt
            const activeCoin = document.querySelector('.ok-left-menu .list-container li.active label.pair').innerText;
            if (activeCoin !== 'USDT') {
                console.log('请先在左侧选中USDT交易');
                return;
            }
            const dateNow = new Date();
            const overSeconds = Math.ceil((dateNow - self.config.lastRefreshTime) / 1000);
            //自动刷新
            if (overSeconds >= self.config.autoRefreshInterval) {
                window.location.reload();
                return;
            }
            if (dateNow < self.config.thawingTime) {
                WebUtils.log(`账号冻结中,等待账号解冻后进行交易计算`, 1);
                return;
            }
            const startDateOfJob = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), self.config.startHoursOfJob, self.config.startMinutesOfJob, 0, 0);
            const endDateOfJob = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), self.config.endHoursOfJob, self.config.endMinutesOfJob, 0, 0);
            if (dateNow < startDateOfJob || dateNow > endDateOfJob) {
                WebUtils.log(`非工作时间段(${self.config.startOfJob}-${self.config.endOfJob})内，等待工作时间开始后进行交易计算`, 1);
                return;
            }
            const buyTradingOrders = result.data.buyTradingOrders;
            const sellTradingOrders = result.data.sellTradingOrders;
            if (buyTradingOrders && buyTradingOrders.length > 0 && sellTradingOrders && sellTradingOrders.length > 0) {
                if (self.config.isDebug) {
                    debugger;
                }
                //未完成订单判断
                if (self.config.unFinishedOrderCount === 2) {
                    WebUtils.log(`存在${self.config.unFinishedOrderCount}单未完成订单，不能自动买卖`, 1);
                    return;
                } else if (self.config.unFinishedOrderCount === 0 && self.config.sellFailed) {
                    //订单已完成或已取消
                    self.config.sellFailed = false;
                    self.config.sellFailedOrderAmount = 0;
                    self.config.diffPrice = self.config.tempDiffPrice;
                    WebUtils.log(`订单已完成或已取消，现已将差异价自动切换回${self.config.diffPrice}`);
                }

                const buy1Order = sellTradingOrders[sellTradingOrders.length - 1];
                const buy1Price = buy1Order.exchangeRate;
                self.config.nowBuyPrice = buy1Price;
                let buy1OrderAmount = buy1Order.availableAmount; //不能四舍五入，会超出订单金额
                let buy1OrderTotal = buy1Price * buy1OrderAmount;
                if (buy1OrderTotal > self.config.maxAutoTradeAmt) {
                    buy1OrderTotal = self.config.maxAutoTradeAmt;
                    buy1OrderAmount = parseInt(buy1OrderTotal / buy1Price * 100) / 100;
                }
                let thatDiffPrice = 0;
                let resCallback = function (successfulBuyAmount) {
                    if (successfulBuyAmount > 0) {
                        const msg = `Okex C2C 交易成功买入USDT:${successfulBuyAmount}个，请及时付款`;
                        if (!self.config.isDebug) {
                            self.sendReminder(msg);
                        }
                    }
                };
                let worthBuying = false; //是否值得买
                for (let i = 0; i < 5; i++) {
                    let sellOrder = buyTradingOrders[i];;
                    let sellPrice = sellOrder.exchangeRate;
                    let sellOrderAmount = sellOrder.availableAmount;
                    thatDiffPrice = self.mathRound(sellPrice - buy1Price);
                    const sellTotalOrderQty = sellOrder.clientCompletedOrderQuantity + sellOrder.clientCancelledOrderQuantity;
                    const sellFinishRate = sellTotalOrderQty > 0 ? self.mathRound(sellOrder.clientCompletedOrderQuantity / sellTotalOrderQty * 100) : 0;
                    const sellOrderId = sellOrder.publicTradingOrderId;
                    let thatSellOrderAmount = 0;
                    //可用usdt
                    const openTradeCurrencyAvailable = self.config.availableUSDT;
                    //跑路模式
                    if (self.config.isAutoSell && sellPrice >= self.config.autoSellPrice && sellFinishRate >= self.config.finishRate * 100) {
                        thatSellOrderAmount = sellOrderAmount > openTradeCurrencyAvailable ? openTradeCurrencyAvailable : sellOrderAmount;
                        if (thatSellOrderAmount > 0) {
                            self.createOrder({
                                tradePrice: sellPrice,
                                tradeType: 1,
                                diffPrice: thatDiffPrice,
                                orderId: sellOrderId,
                                orderAmount: thatSellOrderAmount,
                                resCallback: function (successfulSellAmount) {
                                    let msg = '';
                                    if (successfulSellAmount > 0) {
                                        msg = `Okex C2C 交易以￥${sellPrice}跑路成功USDT：${thatSellOrderAmount}个，请及时收款放币`;
                                    } else {
                                        msg = `Okex C2C 交易以￥${sellPrice}跑路失败`;
                                    }
                                    if (!self.config.isDebug) {
                                        self.sendReminder(msg);
                                    }
                                }
                            });
                            WebUtils.log(`当前卖出价￥${sellPrice},卖出USDT:${sellOrderAmount}个,准备跑路了`);
                        } else {
                            WebUtils.log(`当前卖价：${sellPrice},USDT:${sellOrderAmount}个，可惜没币了`);
                        }
                        return;
                    }
                    if ((self.config.unFinishedOrderCount === 0 && thatDiffPrice >= self.config.diffPrice) || (self.config.unFinishedOrderCount === 1 && thatDiffPrice >= self.config.diffPrice * 5)) { //存在1单未完成订单时，有5倍利润的话也买
                        if (sellFinishRate < self.config.finishRate * 100) {
                            WebUtils.log(`卖家：${sellOrder.clientName}，以￥${sellOrder.exchangeRate}买入USDT:${sellOrder.availableAmount},完成单数:${sellOrder.clientCompletedOrderQuantity},完成率:${sellFinishRate}%，疑似黑名单用户，已停止计算此次交易信息`);
                            continue;
                        }
                        if (self.config.diffPrice > 0 && i < 5 && (sellPrice - buyTradingOrders[i + 1].exchangeRate >= self.config.diffPrice * 2)) {
                            WebUtils.log(`卖家：${sellOrder.clientName}，以￥${sellOrder.exchangeRate}买入USDT:${sellOrder.availableAmount},完成单数:${sellOrder.clientCompletedOrderQuantity},完成率:${sellFinishRate}%，当前买1价为：￥${buy1Price},卖${i + 2}价为:￥${buyTradingOrders[i + 1].exchangeRate}，极大几率是坑，已停止计算此次交易信息`);
                            continue;
                        }
                        //收益最大化,寻找最高金额的
                        if (i === 0 && buy1OrderAmount > sellOrderAmount && sellOrderAmount < openTradeCurrencyAvailable) {
                            const sellOrder2 = buyTradingOrders[1];
                            const sellPrice2 = sellOrder2.exchangeRate;
                            const sellOrderAmount2 = sellOrder2.availableAmount;
                            const sell2TotalOrderQty = sellOrder2.clientCompletedOrderQuantity + sellOrder2.clientCancelledOrderQuantity;
                            const sellFinishRate2 = sell2TotalOrderQty > 0 ? self.mathRound(sellOrder2.clientCompletedOrderQuantity / sell2TotalOrderQty * 100) : 0;;
                            if (sellPrice2 >= sellPrice && sellFinishRate2 >= self.config.finishRate * 100 && sellOrderAmount2 > sellOrderAmount) {
                                WebUtils.log(`发现更高收益，原卖出价￥${sellPrice},新卖出价:￥${sellPrice2},原USDT个数:${sellOrderAmount},新USDT个数:${sellOrderAmount2}`);
                                sellOrder = sellOrder2;
                                sellPrice = sellPrice2;
                                sellOrderAmount = sellOrderAmount2;
                            }
                            if (sellOrderAmount < openTradeCurrencyAvailable) {
                                const sellOrder3 = buyTradingOrders[2];
                                const sellPrice3 = sellOrder3.exchangeRate;
                                const sellOrderAmount3 = sellOrder3.availableAmount;
                                const sell3TotalOrderQty = sellOrder3.clientCompletedOrderQuantity + sellOrder3.clientCancelledOrderQuantity;
                                const sellFinishRate3 = sell3TotalOrderQty > 0 ? self.mathRound(sellOrder3.clientCompletedOrderQuantity / sell3TotalOrderQty * 100) : 0;;
                                if (sellPrice3 >= sellPrice && sellFinishRate3 >= self.config.finishRate * 100 && sellOrderAmount3 > sellOrderAmount) {
                                    WebUtils.log(`发现更高收益，原卖出价￥${sellPrice},新卖出价:￥${sellPrice3},原USDT个数:${sellOrderAmount},新USDT个数:${sellOrderAmount3}`);
                                    sellOrder = sellOrder3;
                                    sellPrice = sellPrice3;
                                    sellOrderAmount = sellOrderAmount3;
                                }
                            }
                        }
                        //卖出金额
                        buy1OrderAmount = buy1OrderAmount <= openTradeCurrencyAvailable || buy1Price <= this.config.autoBuyPrice ? buy1OrderAmount : openTradeCurrencyAvailable;
                        if (sellOrderAmount >= buy1OrderAmount) {
                            thatSellOrderAmount = buy1OrderAmount;
                        } else if (buy1Price <= this.config.autoBuyPrice) {
                            thatSellOrderAmount = 0;
                        } else {
                            thatSellOrderAmount = sellOrderAmount;
                            buy1OrderAmount = sellOrderAmount;
                            buy1OrderTotal = buy1OrderAmount * buy1Price;
                        }
                        if (buy1OrderAmount === 0 && thatSellOrderAmount === 0) {
                            WebUtils.log(`买卖金额不能为0`);
                            return;
                        }
                        resCallback = function (successfulBuyAmount) {
                            if (thatSellOrderAmount > 0 && successfulBuyAmount > 0) {
                                //卖出
                                self.createOrder({
                                    tradePrice: sellPrice,
                                    tradeType: 1,
                                    diffPrice: thatDiffPrice,
                                    orderId: sellOrderId,
                                    orderAmount: successfulBuyAmount,
                                    resCallback: function (successfulSellAmount) {
                                        let msg = '';
                                        if (successfulSellAmount > 0) {
                                            msg = `Okex C2C 交易以￥${buy1Price}买入USDT：${successfulBuyAmount}个，以￥${sellPrice}卖出USDT：${successfulSellAmount}个，请及时收款及付款`;
                                            if (self.config.sellFailed) {
                                                msg = `Okex C2C 交易以￥${sellPrice}卖出USDT：${successfulSellAmount}个，请及时收款及付款`;
                                                self.config.sellFailed = false;
                                                self.config.sellFailedOrderAmount = 0;
                                                self.config.diffPrice = self.config.tempDiffPrice;
                                                WebUtils.log(`卖出成功，现已将差异价自动切换回${self.config.diffPrice}`);
                                            }
                                        } else {
                                            msg = `Okex C2C 交易以￥${buy1Price}买入USDT：${successfulBuyAmount}个，以￥${sellPrice}卖出失败，请及时付款及卖出`;
                                            if (self.config.sellFailed) {
                                                msg = `Okex C2C 交易以￥${sellPrice}卖出USDT：${successfulBuyAmount}个失败，请及时卖出`;
                                            }
                                            self.config.sellFailed = true;
                                            self.config.sellFailedOrderAmount = successfulBuyAmount;
                                            //TODO 暂时不切换差异价
                                            // self.config.diffPrice = 0;
                                            WebUtils.log(`卖出失败，现已将差异价自动切换为${self.config.diffPrice}进行卖出，防止亏损`);
                                        }
                                        if (!self.config.isDebug) {
                                            self.sendReminder(msg);
                                        }
                                    }
                                });
                                worthBuying = true;
                            } else {
                                self.config.autoBuyPrice -= 0.05;
                            }
                        };
                        break;
                    }
                }
                WebUtils.log(`buy1Price:${buy1Price}`, 1);

                if (buy1Price <= self.config.autoBuyPrice || worthBuying) {
                    if (!self.config.sellFailed || (worthBuying && self.config.diffPrice > 0)) {
                        const orderId = buy1Order.publicTradingOrderId;
                        //下单
                        self.createOrder({
                            tradePrice: buy1Price,
                            tradeType: 0,
                            diffPrice: thatDiffPrice,
                            orderId: orderId,
                            orderAmount: buy1OrderAmount,
                            resCallback: resCallback
                        });
                    } else if (worthBuying && resCallback && self.config.sellFailedOrderAmount > 0) {
                        resCallback(self.config.sellFailedOrderAmount);
                    }
                }
            }
        }

        /**
         * 下单
         *
         * @static
         * @param {number} tradePrice 交易价格
         * @param {number} tradeType 交易类型 0-买入 1-卖出
         * @param {number} diffPrice 差异价
         * @param {string} orderId 订单id
         * @param {number} orderAmount 订单金额/usdt
         * @memberof OkexHelper
         */
        createOrder({
            tradePrice,
            tradeType,
            diffPrice,
            orderId,
            orderAmount,
            tryCount = 1,
            resCallback
        }) {
            const self = this;
            if (self.config.isOrdering) {
                WebUtils.log(`正在下单中，并发了...`);
                return;
            }
            self.config.isOrdering = true;
            const params = {
                publicTradingOrderId: orderId,
                amount: orderAmount
            };
            if (self.config.isDebug) {
                debugger;
                // params.publicTradingOrderId += '--debug';
                if (resCallback) {
                    resCallback(orderAmount);
                }
                return;
            }
            //买入成功后先卖出再继续购买
            if (tradeType === 0 && !self.config.canContinuedBuying) {
                return;
            }
            let notifyMsg = '';
            const urlConst = self.getUrlConst();
            self.okFetch(urlConst.POST_OPEN_ORDER, params, {
                method: "POST"
            }).then((result) => {
                self.config.isOrdering = false;
                if (self.config.isDebug) {
                    debugger;
                }
                if (result.code === 0) {
                    switch (tradeType) {
                        case 0:
                            notifyMsg = `当前买1价为:${tradePrice},买卖差价为：${diffPrice.toFixed(2)},第${tryCount}次自动买入${orderAmount}usdt\r\n自动买入结果：${JSON.stringify(result)}`;
                            WebUtils.log(notifyMsg);
                            self.okexNotify('Okex自动买入通知', notifyMsg);
                            self.config.canContinuedBuying = false;
                            self.config.unFinishedOrderCount += 1;
                            break;
                        case 1:
                            notifyMsg = `当前卖1价为:${tradePrice},买卖差价为：${diffPrice.toFixed(2)},第${tryCount}次自动卖出${orderAmount}usdt\r\n自动卖出结果：${JSON.stringify(result)}`;
                            WebUtils.log(notifyMsg);
                            self.okexNotify('Okex自动卖出通知', notifyMsg);
                            self.config.canContinuedBuying = true;
                            self.config.unFinishedOrderCount += 1;
                            break;
                        default:
                            WebUtils.log(`交易类型有误`);
                            break;
                    }
                    //先停止，每次最多两张未完成订单，成功就不买了，预留卖出位置
                    // if (tradeType === 0) {
                    //     self.config.orderTradeBuyObserve.disconnect();
                    // }
                    // if (tradeType === 1) {
                    //     self.config.orderTradeSellObserve.disconnect();
                    // }
                    //回调
                    if (resCallback) {
                        resCallback(orderAmount);
                    }
                } else {
                    switch (tradeType) {
                        case 0:
                            notifyMsg = `当前买1价为:${tradePrice},买卖差价为：${diffPrice.toFixed(2)},第${tryCount}次自动买入${orderAmount}usdt失败\r\n失败原因：${JSON.stringify(result)}`;
                            WebUtils.log(notifyMsg);
                            // self.okexNotify('Okex自动买入通知', notifyMsg);
                            break;
                        case 1:
                            notifyMsg = `当前卖1价为:${tradePrice},买卖差价为：${diffPrice.toFixed(2)},第${tryCount}次自动卖出${orderAmount}usdt失败\r\n失败原因：${JSON.stringify(result)}`;
                            WebUtils.log(notifyMsg);
                            // self.okexNotify('Okex自动卖出通知', notifyMsg);
                            break;
                        default:
                            WebUtils.log(`交易类型有误`);
                            break;
                    }
                    //您的账号因取消订单过多，今天已被限制下单
                    if (result.code === 13046) {
                        WebUtils.log(notifyMsg);
                        const dateNow = new Date();
                        //解冻时间为第二天
                        self.config.thawingTime = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate(), 23, 59, 59, 999);
                        return;
                    }
                    //您的下单数量超过交易单最大委托数，请重新调整
                    if (result.code === 13065 /*&& diffPrice > self.config.diffPrice * 2*/) {
                        if (tryCount <= 2) {
                            orderAmount = parseInt(orderAmount * 100 / 2) / 100;
                            tryCount += 1;
                            //调整数量下单
                            self.createOrder({
                                tradePrice: tradePrice,
                                tradeType: tradeType,
                                diffPrice: diffPrice,
                                orderId: orderId,
                                orderAmount: orderAmount,
                                tryCount: tryCount,
                                resCallback: resCallback
                            });
                            return;
                        }
                    }
                    if (resCallback) {
                        resCallback(0);
                    }
                }
            }).catch((reason) => {
                self.config.isOrdering = false;
                if (self.config.isDebug) {
                    debugger;
                }
                WebUtils.log(JSON.stringify(reason), 4);
            });
        }

        /**
         * 获取okex url
         *
         * @returns 所有url
         * @memberof OkexHelper
         */
        getUrlConst() {
            const API_VERSION = "v2",
                URL_PRE = "/" + API_VERSION,
                URL_CONST = {
                    GET_PRODUCT_LIST: URL_PRE + "/c2c-open/currencies/group",
                    GET_TRADING_ORDER: URL_PRE + "/c2c-open/tradingOrders/group",
                    GET_USER_PHONE: URL_PRE + "/c2c-open/user/phone",
                    GET_OPEN_USER_KYC: URL_PRE + "/c2c-open/user/kyc",
                    POST_TRADING_ORDER: URL_PRE + "/c2c-open/tradingOrder",
                    GET_MY_ORDERS: URL_PRE + "/c2c-open/tradingOrders/my",
                    GET_USER_ASSET: URL_PRE + "/c2c-open/balance/{0}",
                    POST_CANCEL_MY_ORDER: URL_PRE + "/c2c-open/tradingOrder/{0}/cancel",
                    POST_OPEN_ORDER: URL_PRE + "/c2c-open/order",
                    GET_OPEN_ORDER_LIST: URL_PRE + "/c2c-open/orders",
                    POST_OPEN_CONFIRM_PAYMENT: URL_PRE + "/c2c-open/order/{0}/payment-paid",
                    POST_OPEN_CONFIRM_RECEIPT: URL_PRE + "/c2c-open/order/{0}/payment-confirmed",
                    POST_CHANGE_ORDER_ACCEPTOR: URL_PRE + "/c2c-open/tradingOrder/accept-order",
                    GET_CURRENCY_PLATFORM_FEE: URL_PRE + "/c2c-open/currency/{0}",
                    POST_CONTACT_EACH_OTHER: URL_PRE + "/c2c-open/order/privacy-number/{0}",
                    POST_CHANGE_CONTACT_NUMBER: URL_PRE + "/c2c-open/order/refresh-privacy-number/{0}",
                    GET_INDEX_TICKER: URL_PRE + "/market/index/ticker?symbol=f_usd_{0}",
                    GET_INDEX_KLINE: URL_PRE + "/market/index/kLine?symbol=f_usd_{0}&type=1min&limit=300&since=0",
                    GET_OPEN_ORDER_DETAIL: URL_PRE + "/c2c-open/order/{0}",
                    POST_FROZEN_TRADE_ORDER: URL_PRE + "/c2c-open/order/{0}/payment-rejected",
                    POST_CANCEL_ORDER: URL_PRE + "/c2c-open/order/{0}/cancel",
                    GET_FIRST_ENTRANCE: URL_PRE + "/c2c/first-entrance",
                    POST_UPDATE_FIRST_ENTRANCE: URL_PRE + "/c2c/entrance",
                    GET_ORDER_DETAIL: URL_PRE + "/c2c/order/{0}",
                    POST_INDEX_CONFIRM_PAYMENT: URL_PRE + "/c2c/order/{0}/payment-paid",
                    POST_INDEX_CONFIRM_RECEIPT: URL_PRE + "/c2c/order/{0}/payment-confirmed",
                    POST_INDEX_CONTACT_EACH_OTHER: URL_PRE + "/c2c/order/privacy-number/{0}",
                    POST_INDEX_CHANGE_CONTACT_NUMBER: URL_PRE + "/c2c/order/refresh-privacy-number/{0}",
                    POST_INDEX_FROZEN_TRADE_ORDER: URL_PRE + "/c2c/order/{0}/payment-rejected",
                    POST_INDEX_CANCEL_ORDER: URL_PRE + "/c2c/order/{0}/cancel",
                    GET_INDEX_SYMBOL_LIST: URL_PRE + "/c2c/currencies",
                    GET_INDEX_USER_ASSET: URL_PRE + "/c2c/balance/{0}",
                    POST_SUBMIT_TRANSFACTION: URL_PRE + "/c2c/order",
                    GET_INDEX_USER_KYC: URL_PRE + "/c2c/user/kyc",
                    GET_SIMPLE_ORDER: URL_PRE + "/c2c/order/{0}/simple",
                    GET_INDEX_ORDER_LIST: URL_PRE + "/c2c/orders",
                    GET_ENTER_RECEIVING_PERMISSION: URL_PRE + "/c2c/user/current",
                    GET_ACCEPT_ORDER_STATUS: URL_PRE + "/c2c/currencies/accept-order-status",
                    POST_CHANGE_ORDER_STATUS: URL_PRE + "/c2c/currency/{0}/accept-order-status",
                    GET_CURRENCY_SYMBOL_LIST: URL_PRE + "/c2c/wallet-digital-info",
                    GET_WALLER_NATIVE_SYMBOL_INFO: URL_PRE + "/c2c/wallet-legal-info/{0}",
                    POST_UPDATE_BALANCE: URL_PRE + "/c2c/balance",
                    GET_ALL_CURRENCY: URL_PRE + "/c2c/currencies",
                    POST_CHANGE_RECEIPT_MONEY_WAY: URL_PRE + "/c2c/receipt/{0}/status",
                    GET_WALLET_HISTORYS: URL_PRE + "/c2c/wallet-historys/{0}/{1}",
                    POST_UPLOAD_IMG: URL_PRE + "/c2c-open/oss-upload"
                };
            return URL_CONST;
        }

        /**
         * 请求
         *
         * @memberof OkexHelper
         */
        okFetch(url, reqParams, reqOpt) {
            const self = this;
            if (self.config.isDebug) {
                debugger;
            }
            reqOpt = reqOpt || {}
            if ("get" === reqOpt.method.toLocaleLowerCase() && reqParams) {
                var l = "";
                Object.keys(reqParams).forEach(function (e, n) {
                    var a = e + "=" + reqParams[e];
                    l += (n > 0 ? "&" : "?") + a
                }),
                    url += l
            }
            var i = {
                method: reqOpt.method || "GET",
                headers: {
                    Authorization: localStorage.getItem("token") || "",
                    Accept: "application/json",
                    "Content-Type": "application/json"
                },
                credentials: reqOpt.credentials || "same-origin"
            };
            if ("post" === reqOpt.method.toLocaleLowerCase()) {
                i.body = reqOpt.isQueryData ? self.objToQueryString(reqParams) : JSON.stringify(reqParams)
            }
            return fetch(url, i).then((res) => {
                return res.json();
            });
        }

        /**
         * objToQueryString
         *
         * @param {any} e
         * @returns
         * @memberof OkexHelper
         */
        objToQueryString(e) {
            if (!e)
                return "";
            var t = [];
            for (var n in e)
                e.hasOwnProperty(n) && t.push(encodeURIComponent(n) + "=" + encodeURIComponent(e[n]));
            return t.join("&")
        }
    }

    //自动交易
    const okexHelper = new OkexHelper({
        autoBuyPrice: 6.47,
        autoSellPrice: 6.8,
        diffPrice: 0.01,
        maxAutoTradeAmt: 10000,
        finishRate: 0.8,
        isAutoSell: false,
        isDebug: false,
        startOfJob: '08:30',
        endOfJob: '23:59',
        autoRefreshInterval: 60 * 30
    });
    WebUtils.taskRun(() => {
        //模块设置
        webpackJsonp([1], [function (e, t, n) {
            for (const key in n.c) {
                if (n.c.hasOwnProperty(key)) {
                    const esModule = n.c[key];
                    //请求时间配置
                    if (esModule.exports && esModule.exports.default && esModule.exports.default.entrustTime) {
                        const timeConfig = esModule.exports.default;
                        timeConfig.entrustTime = 0.3;
                        break;
                    }
                }
            }
        }])
    });
    okexHelper.autoOrderTrade();
    //16930 G.default.entrustTime=0.5;
    //window.webpackJsonp
})();